#include "AreaNode.h"

#include <osg/Geometry>
#include <osg/Vec4>
#include <osgUtil/Tessellator>

#include <vector>
#include <iostream>

#include <cassert>


AreaNode::AreaNode(osg::ref_ptr<osg::Vec3Array> outerVertices, QList<osg::ref_ptr<osg::Vec3Array> > innerVertices, QString featureCategory, QString name, unsigned int category)
    : ToggleNode(),
      m_category(featureCategory),
      m_name(name)
{
	styleArea(category);
	osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;

    // merge geometry to one vertex array
    osg::ref_ptr<osg::Vec3Array> mergedVertices = new osg::Vec3Array;
    unsigned int numInnerVertices = 0;
    std::vector<int> indices;
    indices.push_back(outerVertices->size());
    
    QListIterator< osg::ref_ptr<osg::Vec3Array> > it(innerVertices);
    while(it.hasNext())
    {
        numInnerVertices += it.next()->size();
        indices.push_back(numInnerVertices);
    }

    //////////////////////////////////////////////////////////////////////////
    // merge Vertices

    // insert outer vertices
    mergedVertices->insert(mergedVertices->end(), outerVertices->begin(), outerVertices->end());

    // insert inner vertices
    for(int i = 0; i < innerVertices.size(); ++i)
    {
        mergedVertices->insert(mergedVertices->end(), innerVertices[i]->begin(), innerVertices[i]->end());
    }

    geometry->setVertexArray(mergedVertices.get());

    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array();
    colors->push_back(m_color);
    geometry->setColorArray(colors.get());
    geometry->setColorBinding(osg::Geometry::BIND_OVERALL);

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
    normals->push_back(osg::Vec3(0.f, -1.f, 0.f));
    geometry->setNormalArray(normals.get());
    geometry->setNormalBinding(osg::Geometry::BIND_OVERALL);

    geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, 0, outerVertices->size()));

    for(int i = 0; i < innerVertices.size(); ++i)
    {
        // draw indexed
        geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, indices[i], innerVertices[i]->size()));
    }

    osg::ref_ptr<osgUtil::Tessellator> tscx=new osgUtil::Tessellator;
    tscx->setTessellationNormal(osg::Vec3(0.0,0.0,1.0));
    tscx->setTessellationType(osgUtil::Tessellator::TESS_TYPE_GEOMETRY);
    tscx->setBoundaryOnly(false);
    tscx->setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD);
    tscx->retessellatePolygons(*geometry);

    this->addDrawable(geometry.get());
}

AreaNode::AreaNode(osg::ref_ptr<osg::Vec3Array> vertices, QString featureCategory, QString name, unsigned int category)
    : m_name(name),
      m_category(featureCategory),
      ToggleNode()
{
    styleArea(category);
    osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry;

    osg::ref_ptr<osg::Vec3Array> triangleStrip = NULL;
    if(category == 1 || category == 2 || category == 3 || category == 4) // river or something similar
    {
        triangleStrip = constructPolyboard(vertices.get());
        geometry->setVertexArray(triangleStrip.get());
    }
    else
    {
        geometry->setVertexArray(vertices);
    }

    osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array();
    colors->push_back(m_color);
    geometry->setColorArray(colors.get());
    geometry->setColorBinding(osg::Geometry::BIND_OVERALL);

    osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
    normals->push_back(osg::Vec3(0.f, -1.f, 0.f));
    geometry->setNormalArray(normals.get());
    geometry->setNormalBinding(osg::Geometry::BIND_OVERALL);

    if(category == 1 || category == 2 || category == 3 || category == 4)
    {
        geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, triangleStrip->size()));
    }
    else
    {
        geometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::POLYGON, 0, vertices->size()));
        osg::ref_ptr<osgUtil::Tessellator> tscx=new osgUtil::Tessellator;
        tscx->setTessellationNormal(osg::Vec3(0.0,0.0,1.0));
        tscx->setTessellationType(osgUtil::Tessellator::TESS_TYPE_GEOMETRY);
        tscx->setBoundaryOnly(false);
        tscx->setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD);
        tscx->retessellatePolygons(*geometry);
    }

    this->addDrawable(geometry.get());
}

AreaNode::~AreaNode()
{

}

void AreaNode::styleArea( unsigned int category )
{
	switch(category)
	{
		case 0: // case water
			{
				m_color = osg::Vec4(165.f/255.f, 191.f/255.f, 221.f/255.f, 1.0);
				break;
			}

        case 1: // river
            {
                m_color = osg::Vec4(165.f/255.f, 191.f/255.f, 221.f/255.f, 1.0);
                m_width = 30.0f;
                break;
            }
        case 2: // canal
            {
                m_color = osg::Vec4(165.f/255.f, 191.f/255.f, 221.f/255.f, 1.0);
                m_width = 15.0f;
                break;
            }
        case 3: // stream
            {
                m_color = osg::Vec4(165.f/255.f, 191.f/255.f, 221.f/255.f, 1.0);
                m_width = 10.0f;
                break;
            }
        case 4: // drain / ditch
            {
                m_color = osg::Vec4(165.f/255.f, 191.f/255.f, 221.f/255.f, 1.0);
                m_width = 5.0f;
                break;
            }

        case 5: // forest
            {
                m_color = osg::Vec4(187.f/255.f, 216.f/255.f, 157.f/255.f, 1.0);
                break;
            }

        case 6: // green area
            {
                m_color = osg::Vec4(202.f/255.f, 223.f/255.f, 170.f/255.f, 1.0);
                break;
            }

        case 7: // residential area
            {
                m_color = osg::Vec4(242.f/255.f, 242.f/255.f, 242.f/255.f, 1.0);
                break;
            }
	
		default:
			{
				m_color = osg::Vec4(255.f/255.f, 0.f/255.f, 0.f/255.f, 1.0);
				break;
			}
	}
}

QString AreaNode::getFeatureString()
{
    QString returnString("name: ");
    returnString.append(m_name);
    returnString.append("\ncategory: ");
    returnString.append(m_category);
    return returnString;
}

osg::Vec3 AreaNode::calculateNormal(const osg::Vec3& v1, const osg::Vec3& v2)
{
    // 2D normal
    osg::Vec3 subtract(v2 - v1); 
    float dx = subtract.x();
    float dy = subtract.y();
    osg::Vec3 normal(-dy, dx, 0.0f);
    normal.normalize();

    return normal;
}

osg::Vec3Array* AreaNode::constructPolyboard(osg::Vec3Array* points)
{
    osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;

    float thickness = m_width;

    for(int i = 0; i < points->size(); ++i)
    {
        if(i == 0)
        {
            // hack for degenerated lines
            if(points->size() < 2)
            {
                osg::Vec3 v1(points->at(i));
                osg::Vec3 v2(points->at(i)); 
                osg::Vec3 normal = calculateNormal(v1, v2);

                osg::Vec3 Pi = points->at(i);
                osg::Vec3 Gi(Pi + (normal * thickness));
                osg::Vec3 Hi(Pi - (normal * thickness));

                vertices->push_back(Gi);
                vertices->push_back(Hi);
            }
            else
            {
                osg::Vec3 v1(points->at(i));
                osg::Vec3 v2(points->at(i+1));
                osg::Vec3 normal = calculateNormal(v1, v2);

                osg::Vec3 Pi = points->at(i);
                osg::Vec3 Gi(Pi + (normal * thickness));
                osg::Vec3 Hi(Pi - (normal * thickness));

                vertices->push_back(Gi);
                vertices->push_back(Hi);
            }
        }
        else if(i == points->size()-1)
        {
            osg::Vec3 v1(points->at(i));
            osg::Vec3 v2(points->at(i-1));
            osg::Vec3 normal = calculateNormal(v1, v2);

            osg::Vec3 Pi = points->at(i);
            osg::Vec3 Gi(Pi - (normal * thickness));
            osg::Vec3 Hi(Pi + (normal * thickness));

            vertices->push_back(Gi);
            vertices->push_back(Hi);
        }
        else
        {
            osg::Vec3 v1(points->at(i));
            osg::Vec3 v2(points->at(i+1));
            osg::Vec3 v3(points->at(i-1));

            osg::Vec3 v2v1(v2 - v1);
            osg::Vec3 v3v1(v3 - v1);

            osg::Vec3 averageNormal = calculateNormal(v2v1, v3v1);
            averageNormal = averageNormal * osg::Matrix::scale(1.0f, 1.0f, 1.0f);

            osg::Vec3 Pi = points->at(i);
            osg::Vec3 Gi(Pi - (averageNormal * thickness));
            osg::Vec3 Hi(Pi + (averageNormal * thickness));

            vertices->push_back(Gi);
            vertices->push_back(Hi);
        }
    }

    return vertices.release();
}